package com.aldobucchi.x.flash.geom.decompose
{
	
	import flash.geom.Matrix;
	
	import mx.utils.StringUtil;
	
	import com.aldobucchi.x.Math.MathUtil;
	import com.aldobucchi.x.flash.geom.MatrixUtil;
	
	
	
	/**
	 * 
	 * based on:
	 * http://trac.dojotoolkit.org/browser/dojox/trunk/gfx/decompose.js
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class SVDDecomposition 
	{
		
		
		public var dx:Number;
		
		public var dy:Number;

		public var sx:Number;
		
		public var sy:Number;
		
		public var angle1:Number;
		
		public var angle2:Number;

		
		
		
		public function SVDDecomposition( dx:Number, dy:Number, sx:Number, sy:Number, angle1:Number, angle2:Number )
		{
			this.dx = dx;
			this.dy = dy;
			this.sx = sx;
			this.sy = sy;
			this.angle1 = angle1;
			this.angle2 = angle2;
		}
		
		
		
		
		
		
		
		public function toString( ):String
		{
		
			return StringUtil.substitute( "SVD( dx:{0}, dy:{1}, sx:{2}, sy:{3}, angle1:{4}, angle2:{5} )",
				
				dx, dy, sx, sy, angle1, angle2
							
			 );		
		
		
		}
		

	
	
		
		
		/**
		 * 
		 * uses two close FP ration and their original magnitudes to approximate the result
		 * 
		 * @param r1
		 * @param m1
		 * @param r2
		 * @param m2
		 * @return 
		 * 
		 */		
		private static function calcFromValues( r1:Number,  m1:Number, r2:Number,  m2:Number ):Number
		{
			if( ! isFinite( r1 ) )
			{
				return r2;	// Number
			}
			else if( ! isFinite( r2 ) )
			{
				return r1;	// Number
			}
			
			m1 = Math.abs(m1), m2 = Math.abs(m2);
			
			return ( m1 * r1 + m2 * r2 ) / ( m1 + m2 );	// Number
		}
	
	
	
	

	
		/**
		 * 
		 * decomposes a matrix into [scale, rotate]; no checks are done
		 * 
		 * @param M
		 * @param result
		 * @return 
		 * 
		 */	
		private static function decomposeSR( M:Matrix , result:SVDDecomposition ):SVDDecomposition
		{
		
			var sign:Number 	= MatrixUtil.getScaleSign( M ),
				a:Number 	 	= result.angle1 = (Math.atan2(M.c, M.d) + Math.atan2(-sign * M.b, sign * M.a)) / 2,
				cos:Number 	 	= Math.cos(a), sin:Number = Math.sin(a);
				
				
			result.sx = calcFromValues(M.a / cos, cos, -M.b / sin, sin);
			result.sy = calcFromValues(M.d / cos, cos,  M.c / sin, sin);
			
			return result;
		
		}
		
		
		
		
		/**
		 * 
		 * decomposes a matrix into [rotate, scale]; no checks are done
		 * 
		 * @param M
		 * @param result
		 * @return 
		 * 
		 */		
		private static function decomposeRS( M:Matrix , result:SVDDecomposition ):SVDDecomposition
		{
			var sign	:Number = MatrixUtil.getScaleSign( M ),
				a		:Number = result.angle2 = (Math.atan2(sign * M.c, sign * M.a) + Math.atan2(-M.b, M.d)) / 2,
				cos		:Number = Math.cos(a), sin:Number = Math.sin(a);
				
			result.sx = calcFromValues(M.a / cos, cos,  M.c / sin, sin);
			result.sy = calcFromValues(M.d / cos, cos, -M.b / sin, sin);
		
			return result;
		
		}
		
		
		
		
		
		
		/**
		 * Decompose a 2D matrix into translation, scaling, and rotation components
		 * description: this function decompose a matrix into four logical components: 
		 * translation, rotation, scaling, and one more rotation using SVD.
		 * The components should be applied in following order:
		 * [translate, rotate(angle2), scale, rotate(angle1)]
		 * 
		 * @param matrix
		 * @return 
		 * 
		 */		
		public static function decompose( matrix:Matrix ):SVDDecomposition
		{

			var M:Matrix = matrix;
			
			var result:SVDDecomposition = new SVDDecomposition( M.tx, M.ty, 1, 1, 0, 0 );
				
			
			// detect case: [scale]
			if( MathUtil.eqFP( M.b, 0 ) && MathUtil.eqFP( M.c, 0) )
			{
				result.sx = M.a;
				result.sy = M.d;
				return result;
			}
			
			
			// detect case: [scale, rotate]
			if(  MathUtil.eqFP( M.a * M.c, -M.b * M.d )  )
			{
				return decomposeSR(M, result);
			}
			
			// detect case: [rotate, scale]
			if( MathUtil.eqFP( M.a * M.b, -M.c * M.d ) )
			{
				return decomposeRS( M, result );
			}
			


			// do SVD
			var	MT	:Matrix = MatrixUtil.transpose( M );
			
				var M_MT:Matrix = MatrixUtil.concat( M, MT );
			
			var	u:EigenValueDecomposition  = EigenValueDecomposition.decompose( M_MT );
			

				var MT_M:Matrix = MatrixUtil.concat( MT, M );

			var	v:EigenValueDecomposition  = EigenValueDecomposition.decompose( MT_M );
			

			var U:Matrix = new Matrix( u.vector1.x, u.vector2.x, u.vector1.y, u.vector2.y );


			var VT:Matrix = new Matrix( v.vector1.x, v.vector1.y, v.vector2.x, v.vector2.y );


			var S:Matrix = MatrixUtil.concat(
									MatrixUtil.INVERT, U,
									M,
									MatrixUtil.INVERT, VT 
								);
									
				
			decomposeSR( VT, result );
			S.a *= result.sx;
			S.d *= result.sy;
			
			decomposeRS(U, result);
			S.a *= result.sx;
			S.d *= result.sy;
			
			
			
			result.sx = S.a;
			
			result.sy = S.d;
			

			
			return result;
			
		};
		
		


	}
	
}